////////////////////////////////////////////////////////////////////////////////
// Options
////////////////////////////////////////////////////////////////////////////////

// To log all computation in a log file
#define LOG_COMPUTATION

// To force SC computation to stop at the given amount of computations
#define BATCH_MODE
#ifdef BATCH_MODE
	//#define NB_EXP_COMPUTATIONS 750
	#define MAX_SIM_TIME 300
#endif

////////////////////////////////////////////////////////////////////////////////
// Includes
////////////////////////////////////////////////////////////////////////////////

#include "Plugin.hpp"
#include "macros.h"

// Conversion functions
#include "Convert.hpp"

// Toolit
#include "TKMacros.h"

// IO stream
#include <iostream>

#ifdef LOG_COMPUTATION
	// STL classes
	#include <vector>
	#include <utility>
	#include <string>
	#include <fstream>
#endif

#ifdef BATCH_MODE
	#include "InterruptionException.hpp"
#endif

// Namespaces
using namespace SC;
using namespace std;
using namespace Toolkit;

////////////////////////////////////////////////////////////////////////////////
// Experiment data collection
////////////////////////////////////////////////////////////////////////////////

#ifdef LOG_COMPUTATION
	//static unsigned int nb_computations;
	static unsigned int enzyme1_time; // model time approximation assuming there is only 1 Enzyme_1 in the model (would generally be used once per cycle)
	static vector< pair<unsigned int, unsigned int> > qty_KKKst, qty_KKPP, qty_KPP;
	static string file_name;
#endif

////////////////////////////////////////////////////////////////////////////////
// Plugin init and exit
////////////////////////////////////////////////////////////////////////////////

void InitPlugin (const string & prog_name) {
#ifdef LOG_COMPUTATION
	//nb_computations = 0;
	enzyme1_time = 0;
	qty_KKKst.push_back(pair<unsigned int, unsigned int>(0, 0));
	qty_KKPP.push_back(pair<unsigned int, unsigned int>(0, 0));
	qty_KPP.push_back(pair<unsigned int, unsigned int>(0, 0));
	file_name = prog_name + "_log.m";
#endif
}

void ClosePlugin () {
#ifdef LOG_COMPUTATION
	qty_KKKst.push_back(pair<unsigned int, unsigned int>(enzyme1_time, qty_KKKst.back().second));
	qty_KKPP.push_back(pair<unsigned int, unsigned int>(enzyme1_time, qty_KKPP.back().second));
	qty_KPP.push_back(pair<unsigned int, unsigned int>(enzyme1_time, qty_KPP.back().second));
	// Open log stream
	ofstream log_stream(file_name.c_str(), ios_base::out|ios_base::app);
	if (!log_stream.is_open()) {
		cerr << "Cannot open " << file_name << endl;
		return;
	}
	// Write data to file
	time_t tt = time(NULL);
	char buffer[32];
	strftime(buffer, 32, "%y%m%d_%H%M%S", localtime(&tt));
	// KKK*
	log_stream << "mapk_" << buffer << ".KKKst.x = [";
	for (unsigned int i=0; i<qty_KKKst.size(); ++i)
		log_stream << qty_KKKst[i].first << " ";
	log_stream << "];" << endl;
	log_stream << "mapk_" << buffer << ".KKKst.y = [";
	for (unsigned int i=0; i<qty_KKKst.size(); ++i)
		log_stream << qty_KKKst[i].second << " ";
	log_stream << "];" << endl;
	// KKPP
	log_stream << "mapk_" << buffer << ".KKPP.x = [";
	for (unsigned int i=0; i<qty_KKPP.size(); ++i)
		log_stream << qty_KKPP[i].first << " ";
	log_stream << "];" << endl;
	log_stream << "mapk_" << buffer << ".KKPP.y = [";
	for (unsigned int i=0; i<qty_KKPP.size(); ++i)
		log_stream << qty_KKPP[i].second << " ";
	log_stream << "];" << endl;
	// KKP
	log_stream << "mapk_" << buffer << ".KPP.x = [";
	for (unsigned int i=0; i<qty_KPP.size(); ++i)
		log_stream << qty_KPP[i].first << " ";
	log_stream << "];" << endl;
	log_stream << "mapk_" << buffer << ".KPP.y = [";
	for (unsigned int i=0; i<qty_KPP.size(); ++i)
		log_stream << qty_KPP[i].second << " ";
	log_stream << "];" << endl << endl;
	// Close log stream
	if (log_stream.is_open()) log_stream.close();
#endif
}

////////////////////////////////////////////////////////////////////////////////
// SC Callbacks
////////////////////////////////////////////////////////////////////////////////

#define KKK						"10000"
#define KKKst					"10001"

#define KK						"01000"
#define KKP						"01001"
#define KKPP					"01011"

#define K						"00100"
#define KP						"00101"
#define KPP						"00111"

#define E1						"00000"
#define E2						"00001"
#define KPase					"00010"
#define KKPase					"00011"


#define K_FUNC_OFF				0
#define K_FUNC_LGH				2
#define F_NOP					"00"
#define F_Phosphorylation		"01"
#define F_Dephosphorylation		"10"

#define SetBound(X,A)			X.ReplaceKernel(A, K_BOUND_OFF, K_BOUND_LGH)

#define GetType(X)				X.GetKernel(K_TYPE_OFF, K_TYPE_LGH)
#define SetType(X,T)			X.ReplaceKernel(T, K_TYPE_OFF, K_TYPE_LGH)

#define SetFunction(X,F)		X.ReplaceKernel(F, K_FUNC_OFF, K_FUNC_LGH)

bool Phosphorylation (SCSystem & kinase, const SCSystem & kinase_ctx, SCSystem & phosphate) {
#ifdef LOG_COMPUTATION
	if (GetType(kinase_ctx) == E1) enzyme1_time += 1;
#endif
	if (frand() <= 0.5f) {
		//cout << "PH " << GetType(kinase) << " " << GetType(kinase_ctx) << " " << GetType(phosphate) << endl;
		// Bind phosphate to kinase
		kinase.Grab(phosphate);
		phosphate.Grab(kinase);
		// Set phosphate as bound
		SetBound(phosphate, "1");
		// Set new kinase type
		string type = GetType(kinase);
		if (type == KKK)		SetType(kinase, KKKst);
		else if (type == KK)	SetType(kinase, KKP);
		else if (type == KKP)	SetType(kinase, KKPP);
		else if (type == K)		SetType(kinase, KP);
		else if (type == KP)	SetType(kinase, KPP);
		else cerr << "Error: " << type << " not recognised as a valid type for phosphorylation" << endl;
		// Eventually activate kinase as reactant
		type = GetType(kinase);
		if ((type == KKKst) || (type == KKPP))
			SetFunction(kinase, F_Phosphorylation);
#ifdef LOG_COMPUTATION
		//++nb_computations;
		if (type == KKKst)
			qty_KKKst.push_back(pair<unsigned int, unsigned int>(enzyme1_time, qty_KKKst.back().second+1));
		else if (type == KKPP)
			qty_KKPP.push_back(pair<unsigned int, unsigned int>(enzyme1_time, qty_KKPP.back().second+1));
		else if (type == KPP)
			qty_KPP.push_back(pair<unsigned int, unsigned int>(enzyme1_time, qty_KPP.back().second+1));
#endif
#ifdef BATCH_MODE
		//if (nb_computations >= NB_EXP_COMPUTATIONS)
		if (enzyme1_time >= MAX_SIM_TIME)
			throw Exceptions::InterruptionException("Experiment finished");
#endif
		// Computation occurred
		return true;
	}
	return false;
}

bool Dephosphorylation (SCSystem & kinase, const SCSystem & pase_ctx, SCSystem & phosphate) {
	if (frand() <= 0.5f) {
		//cout << "DE " << GetType(kinase) << " " << GetType(pase_ctx) << " " << GetType(phosphate) << endl;
		// Free phosphate to kinase
		kinase.Release(phosphate);
		phosphate.Release(kinase);
		// Set phosphate as free
		SetBound(phosphate, "0");
		// Set new kinase type
		string type = GetType(kinase);
		if (type == KKKst)		SetType(kinase, KKK);
		else if (type == KKP)	SetType(kinase, KK);
		else if (type == KKPP)	SetType(kinase, KKP);
		else if (type == KP)	SetType(kinase, K);
		else if (type == KPP)	SetType(kinase, KP);
		else cerr << "Error: " << type << " not recognised as a valid type for dephosphorylation" << endl;
		// Desactivate kinase from now on as not reactant any more
		SetFunction(kinase, F_NOP);
#ifdef LOG_COMPUTATION
		//++nb_computations;
		if (type == KKKst)
			qty_KKKst.push_back(pair<unsigned int, unsigned int>(enzyme1_time, qty_KKKst.back().second-1));
		else if (type == KKPP)
			qty_KKPP.push_back(pair<unsigned int, unsigned int>(enzyme1_time, qty_KKPP.back().second-1));
		else if (type == KPP)
			qty_KPP.push_back(pair<unsigned int, unsigned int>(enzyme1_time, qty_KPP.back().second-1));
#endif
#ifdef BATCH_MODE
		//if (nb_computations >= NB_EXP_COMPUTATIONS)
		if (enzyme1_time >= MAX_SIM_TIME)
			throw Exceptions::InterruptionException("Experiment finished");
#endif
		// Computation occurred
		return true;
	}
	return false;
}

////////////////////////////////////////////////////////////////////////////////
